home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (c) 2009 Bui Viet Thanh (thanhbv@gmail.com).
- *
- * This file is part of clicknlearn.
- *
- * clicknlearn is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * clicknlearn is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with clicknlearn. If not, see <http://www.gnu.org/licenses/>.
- */
-
- Components.utils.import("resource://clicknlearn/cnldao.js");
-
- function Clicknlearn() {
- window.addEventListener("click", this.eventClick, false);
- window.addEventListener("load", this.eventLoad, false);
- window.addEventListener("contextmenu", this.eventContextMenu, true);
- window.addEventListener("keypress", this.eventKeyPress, false);
- this.modifier = 1;//shiftKey + 2 * ctrlKey + 4 * altKey
- this.maxWidth = 640;
- //static:
- this.BOUNDARY_NODES = ['BODY', 'BR', 'CAPTION', 'CENTER', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'INPUT', 'LI', 'OL', 'P', 'PRE', 'SCRIPT', 'SELECT', 'TD', 'TEXTAREA', 'TR', 'UL'];
- }
-
- Clicknlearn.prototype.eventLoad = function(event) { CNL.eventLoadImpl(event); }
- Clicknlearn.prototype.eventLoadImpl = function(event) {
- this.currentDoc = null;
- this.eventClientX = null;
- this.eventClientY = null;
- //the following statement must place here (not in clicknlearn.xul)
- // because if not => document.getElementById("string-bundle") will == null !
- this.stringsBundle = document.getElementById("string-bundle");
- //Oh! see https://wiki.mozilla.org/Labs/JS_Modules#StringBundle
- //we can initialized stringsBundle by the following statement:
- // this.stringsBundle = new StringBundle("chrome://clicknlearn/locale/string-bundle.properties");
- //Oh! :D https://wiki.mozilla.org/Labs/JS_Modules is still LAB!
- //& be implemented in Weave: resource://weave/ext/[StringBundle | Observers | Preferences].js,
- this.DICLOOKUP = new DicLookup();
- this.REMIND = new Remind();
- this.clickIdHash = {"requestWordFromOtherSourcesButton": [DicLookup.prototype.requestWordFromOtherSources, this.DICLOOKUP]
- , "remindButton": [Remind.prototype.remindCurrentWord, this.REMIND]
- , "removeWordButton": [Remind.prototype.removeCurrentWord, this.REMIND]
- , "cnl_settingsButton": [this.showPrefsWindow, this]};
- var appcontent = document.getElementById("appcontent");
- if(appcontent)
- appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
- }
-
- Clicknlearn.prototype.showPrefsWindow = function(){
- window.open("chrome://clicknlearn/content/options.xul", "optionsWindow", "chrome,centerscreen");
- }
-
- /**
- * do something with the loaded page.
- */
- Clicknlearn.prototype.onPageLoad = function(event){
- CNL.currentDoc = event.originalTarget; // doc is document that triggered "onload" event
- CNL.attachCss();
- CNL.REMIND.highlightWords();
- }
-
- /**
- * To fix issue 3:
- * [linux only] shift + right-click on a word still open the context menu
- */
- Clicknlearn.prototype.eventContextMenu = function(event) { CNL.eventContextMenuImpl(event); }
- Clicknlearn.prototype.eventContextMenuImpl = function(event) {
- // Return if the click was not on an HTML document.
- if (event.target.ownerDocument != "[object XPCNativeWrapper [object HTMLDocument]]")
- return;
- // Check if the correct mouse button and the correct modifier were pressed. If so, look the word up.
- // & prevent the context menu from opening
- if (event.button == 2 && this.modifierIsPressed(event, this.modifier))
- event.preventDefault();
- }
-
- /**
- * remove lookup div when user press ESC key
- */
- Clicknlearn.prototype.eventKeyPress = function(event) { CNL.eventKeyPressImpl(event); }
- Clicknlearn.prototype.eventKeyPressImpl = function(event) {
- if(this.DICLOOKUP.div == null)
- return;
- // alert(event.keyCode + ", " + KeyEvent.DOM_VK_ENTER); ??? 13 & 14
- if(event.keyCode == KeyEvent.DOM_VK_ESCAPE)
- this.DICLOOKUP.clearAll();
- else if(event.keyCode == 13){
- var element = event.target;
- // alert(element);
- if(element.id == 'cnlwordbox' && element.value){
- this.DICLOOKUP.currentWord = element.value;
- this.DICLOOKUP.formatCurrentWord();
- this.DICLOOKUP.reset();
- this.DICLOOKUP.div = this.attachDiv(this.DICLOOKUP.divId);
- this.DICLOOKUP.printLookingUp();
- this.DICLOOKUP.requestWord();
- }
- }
- }
-
- Clicknlearn.prototype.eventClick = function(event) { CNL.eventClickImpl(event); }
- Clicknlearn.prototype.eventClickImpl = function(event) {
- // Return if the click was not on an HTML document.
- if (event.target.ownerDocument != "[object XPCNativeWrapper [object HTMLDocument]]")
- return;
- // Check if the correct mouse button and the correct modifier were pressed. If so, look the word up.
- if (event.button == 2 && this.modifierIsPressed(event, this.modifier))
- this.lookUpWord(event);
-
- // If it's a left-click, hide the lookup div
- if (event.button == 0 && this.DICLOOKUP.div != null) {
- var element = event.target;
- var clickOnElementFunc = this.clickIdHash[element.id];
- if(clickOnElementFunc != undefined){
- clickOnElementFunc[0].apply(clickOnElementFunc[1]);
- return;
- }
- var clickTarget = "";
- while (element != null) {
- if(element.id == this.DICLOOKUP.divId){
- clickTarget = this.DICLOOKUP.divId;
- break;
- }
- else
- element = element.parentNode;
- }
- if (clickTarget != this.DICLOOKUP.divId)
- this.DICLOOKUP.clearAll();
- }
- }
-
- Clicknlearn.prototype.modifierIsPressed = function(event, modifier) {
- return event.shiftKey + 2 * event.ctrlKey + 4 * event.altKey == modifier;
- }
-
- /**
- * get current word, and delegate the work looking up the definition from internet to a DicLookup object
- * @param event click event use to get the word and sentence that the user click on
- */
- Clicknlearn.prototype.lookUpWord = function(event) {
- this.getCurrentWordAndSentence(event.rangeParent, event.rangeOffset);
- this.currentDoc = event.target.ownerDocument;
- this.eventClientX = event.clientX;
- this.eventClientY = event.clientY;
- this.DICLOOKUP.reset();
- this.DICLOOKUP.div = this.attachDiv(this.DICLOOKUP.divId);
- if (! this.DICLOOKUP.currentWord){
- this.DICLOOKUP.showDivWithEmptyWord();
- } else {
- this.DICLOOKUP.printLookingUp();
- this.DICLOOKUP.requestWord();
- }
- }
-
- Clicknlearn.prototype.removeDiv = function(div) {
- if (this.currentDoc != null && div != null && div.parentNode != null)
- div.parentNode.removeChild(div);
- }
-
- Clicknlearn.prototype.attachDiv = function(divId) {
- this.attachCss();
- var div = this.currentDoc.createElementNS("http://www.w3.org/1999/xhtml", "div");
- div.id = divId;
- div.className = "clicknlearn";
- div.style.maxWidth = this.maxWidth + "px";
- this.setDivPosition(div);
- return this.currentDoc.body.appendChild(div);
- }
-
- Clicknlearn.prototype.attachCss = function() {
- var cssAlreadyInPlace = this.currentDoc.getElementById("clicknlearnCSS");
- if (cssAlreadyInPlace == null) {
- var heads = this.currentDoc.getElementsByTagName("head");
- var link = this.currentDoc.createElement("link");
- link.rel = "stylesheet";
- link.id = "clicknlearnCSS";
- link.type = "text/css";
- link.href = "chrome://Clicknlearn/content/css.css";
- heads[0].appendChild(link);
- }
- }
-
- Clicknlearn.prototype.setDivPosition = function(div) {
- // Set horizontal position.
- if (this.eventClientX + this.maxWidth + 64 > this.currentDoc.width)
- if (this.currentDoc.width > this.maxWidth)
- div.style.left = this.currentDoc.width - this.maxWidth - 64 + "px";
- else
- div.style.left = "0px";
- else
- div.style.left = this.eventClientX + "px";
-
- // Set vertical position.
- div.style.top = this.eventClientY + this.currentDoc.documentElement.scrollTop + this.currentDoc.body.scrollTop + 10 + "px";
- }
-
- /**
- * Set [this.DICLOOKUP.currentWord, this.DICLOOKUP.currentRemind] = the [word, sentence] that user click on
- * or ["", xxx] if not found
- * @param parent The DOM node that user click on
- * @param offset pos in the parent node that user click on
- */
- Clicknlearn.prototype.getCurrentWordAndSentence = function(parent, offset) {
- // Return the selected text if it was clicked on.
- var selection = document.commandDispatcher.focusedWindow.getSelection();
- this.DICLOOKUP.currentWord = "";
- if (selection != "") {
- var mouseIsOverSelection = ! selection.isCollapsed && selection.getRangeAt(0).isPointInRange(parent, offset);
- if (mouseIsOverSelection){
- this.DICLOOKUP.currentWord = selection.toString();
- this.DICLOOKUP.formatCurrentWord();
- }
- }
- var textNodeLists;
- if(! this.DICLOOKUP.currentWord){
- textNodeLists = this.expandTextNodeLists(parent);
- //TODO haven't been tested
- if(textNodeLists[0].length == 0)
- return;
- this.DICLOOKUP.currentWord = this.findWord(textNodeLists[0], textNodeLists[1], offset);
- this.DICLOOKUP.formatCurrentWord();
- }
- if(this.DICLOOKUP.currentWord){
- let wordData = CNL_DAO.getWordData(this.DICLOOKUP.currentWord);
- if(wordData && wordData.remind){
- this.DICLOOKUP.currentRemind = wordData.remind;
- // this.DICLOOKUP.currentUrl = wordData.originalUrl;
- }
- else{
- if(! textNodeLists)
- textNodeLists = this.expandTextNodeLists(parent);
- this.DICLOOKUP.currentRemind = this.findSentence(textNodeLists[0], textNodeLists[1], offset);
- // this.DICLOOKUP.currentUrl = this.currentDoc.location.toString();
- }
- if(wordData == null)
- this.DICLOOKUP.remember = false;
- else
- this.DICLOOKUP.remember = wordData.read == 0;
- }
- }
-
- /**
- * return [prevTextNodeList, nextTextNodeList]
- */
- Clicknlearn.prototype.expandTextNodeLists = function(node){
- // alert("click on: " + node.textContent);
- ////////////go up to a tag that make a new line
- var startNode = node;
- var iterateToEndTag = false;
- var prevTextNodeList = new Array();
- while(this.isNotBoundaryNode(startNode.nodeName)){
- if(startNode.nodeType == Node.TEXT_NODE && startNode.textContent)
- prevTextNodeList[prevTextNodeList.length] = startNode;
- if(!iterateToEndTag){
- if(startNode.previousSibling){
- startNode = startNode.previousSibling;
- iterateToEndTag = true;
- }
- else
- startNode = startNode.parentNode;
- }
- else{
- if(startNode.lastChild)//not TextNode
- startNode = startNode.lastChild;
- else if(startNode.previousSibling)
- startNode = startNode.previousSibling;
- else{
- startNode = startNode.parentNode;
- iterateToEndTag = false;
- }
- }
- }
- // alert("start break line tag: " + startNode.tagName + iterateToEndTag);
- ////////////go down to a tag that make a new line
- var endNode = node;
- var iterateToStartTag = true;
- var nextTextNodeList = new Array();
- while(this.isNotBoundaryNode(endNode.nodeName)){
- if(endNode.nodeType == Node.TEXT_NODE && endNode.textContent)
- nextTextNodeList[nextTextNodeList.length] = endNode;
- if(iterateToStartTag){
- if(endNode.firstChild)
- endNode = endNode.firstChild;
- else if(endNode.nextSibling)
- endNode = endNode.nextSibling;
- else{
- endNode = endNode.parentNode
- iterateToStartTag = false;
- }
- }
- else{
- if(endNode.nextSibling){
- endNode = endNode.nextSibling;
- iterateToStartTag = true;
- }
- else
- endNode = endNode.parentNode;
- }
- }
- // alert("End break line tag: " + endNode.tagName + iterateToStartTag);
- return [prevTextNodeList, nextTextNodeList];
- }
-
- /**
- * @param boundaryDelimiters
- * @param prevTextNodeList find from prevTextNodeList[0][offset] backward
- * @param nextTextNodeList find from nextTextNodeList[0][offset] forward
- * @param offset offset of the position we want to start finding.
- * It is the offset in prevTextNodeList[0] (== nextTextNodeList[0])
- * @return text, or "" if not found
- */
- Clicknlearn.prototype.findText = function(boundaryDelimiters, prevTextNodeList, nextTextNodeList, offset){
- var parent = prevTextNodeList[0];
- var startOffset = -1, endOffset = -1;//startOffset == -1 mean rangeStartIsNotSet
- var foundPos, startNodeIndex, endNodeIndex;
- //prevTextNodeList
- foundPos = this.findCharListPositionInTextNodeList(boundaryDelimiters /*" \t\n"*/, prevTextNodeList, offset, true);
- startNodeIndex = foundPos[0];
- startOffset = foundPos[1];
- if(startOffset == -1){
- startNodeIndex = prevTextNodeList.length - 1;
- startOffset = 0;
- }
- //nextTextNodeList
- foundPos = this.findCharListPositionInTextNodeList(boundaryDelimiters, nextTextNodeList, offset, false);
- endNodeIndex = foundPos[0];
- endOffset = foundPos[1];
- // //debug
- // var log = "[";
- // for(var i=0; i< nextTextNodeList.length; i++)
- // log += nextTextNodeList[i].textContent + "]["
- // log += "\n\n" + endNodeIndex + ", " + endOffset;
- // alert(log);
- // //~debug
- if(endOffset == -1){
- endNodeIndex = nextTextNodeList.length - 1;
- endOffset = nextTextNodeList[endNodeIndex].textContent.length - 1;
- }
- var range = parent.ownerDocument.createRange();
- range.setStart(prevTextNodeList[startNodeIndex], startOffset);
- range.setEnd(nextTextNodeList[endNodeIndex], endOffset + 1);
- //see http://stackoverflow.com/questions/1586231/strange-behaviour-with-range-tostring
- var sel = content.getSelection(); //use HTML window insteads of XUL window
- var prevSelectedRange;
- //TODO need review. we check because sometimes sel.getRangeAt(0) will raise a exception
- if(sel.rangeCount > 0){
- prevSelectedRange = sel.getRangeAt(0);
- sel.removeAllRanges();
- }
- sel.addRange(range);
- var ret = sel.toString()
- sel.removeAllRanges();
- if (prevSelectedRange != undefined)
- sel.addRange(prevSelectedRange);
- return ret;
- }
-
- /**
- * When find sentence, we must care if the pos that user click on (that we start finding) is in a PRE tag
- */
- Clicknlearn.prototype.findSentence = function(prevTextNodeList, nextTextNodeList, offset){
- var delimiters = ".?!;|";
- var s = this.isInPreTag(parent)?
- this.findText("\n" + delimiters, prevTextNodeList, nextTextNodeList, offset) :
- this.findText(delimiters, prevTextNodeList, nextTextNodeList, offset);
- return CNLUtils.trim(s, "\n " + delimiters);
- }
-
- /**
- * When find word, we don't care if the pos that user click on (that we start finding) is in a PRE tag
- */
- Clicknlearn.prototype.findWord = function(prevTextNodeList, nextTextNodeList, offset){
- return this.findText(" \t\n./", prevTextNodeList, nextTextNodeList, offset);
- }
-
- /**
- * return [foundNodeIndex, foundOffset]
- * or [xxx, -1] if not found
- * @param charList the chatList to find
- * @param textNodeList text node list to find in
- * @param offsetInFirstNode find from pos textNodeList[0][offsetInFirstNode]
- * @param findBackward true if find backward
- */
- Clicknlearn.prototype.findCharListPositionInTextNodeList = function(charList, textNodeList, offsetInFirstNode, findBackward){
- var foundNodeIndex = textNodeList.length - 1, offset, i, j, foundOffset;
- if(findBackward){
- foundOffset = -1;
- for(j=0; j<charList.length; j++){
- offset = textNodeList[0].textContent.lastIndexOf(charList[j], offsetInFirstNode);
- if(offset != -1){
- if(foundNodeIndex > 0 || foundNodeIndex == 0 && foundOffset < offset){
- foundNodeIndex = 0;
- foundOffset = offset;
- }
- }
- else
- for(i=1; i <= foundNodeIndex; i++){
- offset = textNodeList[i].textContent.lastIndexOf(charList[j]);
- if(offset != -1){
- if(foundNodeIndex > i || foundNodeIndex == i && foundOffset < offset){
- foundNodeIndex = i;
- foundOffset = offset;
- break;
- }
- }
- }
- }
- }
- else{
- foundOffset = 1000;
- for(j=0; j<charList.length; j++){
- offset = textNodeList[0].textContent.indexOf(charList[j], offsetInFirstNode);
- if(offset != -1){
- if(foundNodeIndex > 0 || foundNodeIndex == 0 && foundOffset > offset){
- foundNodeIndex = 0;
- foundOffset = offset;
- }
- }
- else
- for(i=1; i <= foundNodeIndex; i++){
- offset = textNodeList[i].textContent.indexOf(charList[j]);
- if(offset != -1){
- // alert("find backward: "+ foundNodeIndex + ", " + i + ", " + foundOffset + ", " + offset);
- if(foundNodeIndex > i || foundNodeIndex == i && foundOffset > offset){
- foundNodeIndex = i;
- foundOffset = offset;
- break;
- }
- }
- }
- }
- if(foundOffset == 1000)
- foundOffset = -1;
- }
- return [foundNodeIndex, foundOffset];
- }
-
- Clicknlearn.prototype.isInPreTag = function(node){
- while(node.parentNode){
- node = node.parentNode;
- if(node.tagName == "PRE")
- return true;
- }
- return false;
- }
-
- /**
- * returns true if the passed node name is not a "boundary" node
- */
- Clicknlearn.prototype.isNotBoundaryNode = function(nodeName) {
- return CNLUtils.search(this.BOUNDARY_NODES, nodeName) == -1;
- }
-